package vg.services.graph_view_manager.realization.graph_comparison.algorithm;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

/**
 * This class generates solutions.
 *
 * @author Timur Zolotuhin (e-mail: tzolotuhin@gmail.com)
 */
public class SolutionSet {
	// Main data
    private BigDecimal upperBoundCountCases;

    private float[][] relationshipTable;
	private float barrier;

    private List<BiGraphEdgeGroup> allGroups;
    private List<BiGraphEdgeGroup> reverseAllGroups;

	private InnerEdgeGroupSet allGroupsSet;
	private InnerEdgeGroupSet currGroupSet = null;

    /**
     * Constructor.
     * @param relationshipTable - not null
     * @param barrier - in range from 0 to 1
     * @throws IllegalArgumentException - if relationshipTable is null or barrier isn't in range from 0 to 1.
     */
	public SolutionSet(float[][] relationshipTable, float barrier) throws IllegalArgumentException {
		// check arguments
        if (relationshipTable == null || barrier > 1 || barrier < 0)
            throw new IllegalArgumentException("relationshipTable == null || barrier > 1 || barrier < 0");

        // initialize arguments
        this.relationshipTable = relationshipTable;
		this.barrier = barrier;
		init();
	}
	
	public Solution next() {
        if (!hasNext())
            return null;

        BiGraphEdge[] solution = currGroupSet.next();

        if (solution == null) {
            nextCurrGroups();
            return next();
        }

        return new Solution(solution);
    }

	public boolean hasNext() {
        return (currGroupSet != null && currGroupSet.hasNext()) || allGroupsSet.hasNext();
	}
	
	public BigDecimal upperBoundCountCases() {
        return upperBoundCountCases;
	}

//=============================================================================
//-----------------PRIVATE METHODS---------------------------------------------
	private void init() {
        // initialize columnSize, rowSize
        int columnSize = 0;
        int rowSize = 0;

        if (relationshipTable.length > 0)
            columnSize = relationshipTable.length;

        if (columnSize > 0)
            rowSize = relationshipTable[0].length;

		// initialize tmpColumnGroup and tmpRowGroup
        BiGraphEdgeGroup[] tmpColumnGroup = new BiGraphEdgeGroup[columnSize];
		for (int i = 0; i < columnSize; i++) {
            tmpColumnGroup[i] = new BiGraphEdgeGroup();
		}

		BiGraphEdgeGroup[] tmpRowGroup = new BiGraphEdgeGroup[rowSize];
		for (int j = 0; j < rowSize; j++) {
            tmpRowGroup[j] = new BiGraphEdgeGroup();
		}
		for (int i = 0; i < columnSize; i++) {
			for (int j = 0; j < rowSize; j++) {
				if (relationshipTable[i][j] >= barrier) {
					BiGraphEdge e = new BiGraphEdge(i, j);
                    tmpColumnGroup[i].push(e);
                    tmpRowGroup[j].push(e);
				}
			}
		}

        // initialize allGroups and create allGroupSet
        allGroups = new ArrayList<BiGraphEdgeGroup>();
		for (int i = 0; i < columnSize; i++) {
			if (tmpColumnGroup[i].size() > 0)
                allGroups.add(tmpColumnGroup[i]);
		}

        allGroupsSet = new InnerEdgeGroupSet(allGroups);

        // initialize reverseAllGroups
		reverseAllGroups = new ArrayList<BiGraphEdgeGroup>();
		for (int i = 0; i < rowSize; i++) {
			if (tmpRowGroup[i].size() > 0)
                reverseAllGroups.add(tmpRowGroup[i]);
		}

        // calculate roughSize
        upperBoundCountCases = new BigDecimal(1);
        for (BiGraphEdgeGroup group : allGroups) {
            upperBoundCountCases = upperBoundCountCases.multiply(new BigDecimal(group.size()));
        }

        nextCurrGroups();
	}

//==============================================================================
//-----------------PRIVATE METHODS----------------------------------------------
    private void nextCurrGroups() {
        List<BiGraphEdgeGroup> currGroups = new ArrayList<BiGraphEdgeGroup>();

        //while(currGroups.size() < Math.min(allGroups.size(), reverseAllGroups.size())) {
            // build column solution
            BiGraphEdge[] preventColumnSolutionArray = allGroupsSet.next();
            if (preventColumnSolutionArray == null)
                return;

            // build row solution
            BiGraphEdgeGroup[] preventRowSolutionArray = new BiGraphEdgeGroup[reverseAllGroups.size()];
            for (int i = 0; i < reverseAllGroups.size(); i++) {
                preventRowSolutionArray[i] = new BiGraphEdgeGroup();
            }

            for (BiGraphEdge bufEdge : preventColumnSolutionArray) {
                int i = 0;
                for (BiGraphEdgeGroup bufGroup : reverseAllGroups) {
                    if (bufGroup.contains(bufEdge)) {
                        preventRowSolutionArray[i].push(bufEdge);
                        break;
                    }
                    i++;
                }
            }

            // clear empty groups
            currGroups = new ArrayList<BiGraphEdgeGroup>();
            for (BiGraphEdgeGroup buf : preventRowSolutionArray) {
                if (buf != null && buf.size() > 0) {
                    currGroups.add(buf);
                }
            }
        //}

        // create currGroupSet
        currGroupSet = new InnerEdgeGroupSet(currGroups);
	}

//==============================================================================
//-----------------PRIVATE CLASSES----------------------------------------------
    private static class InnerEdgeGroupSet {
        private List<BiGraphEdgeGroup> groups;
        private int[] upperBound;
        private int[] curr;

        /**
         * Constructor.
         * @param groups - each element of groups must be not null.
         */
        public InnerEdgeGroupSet(List<BiGraphEdgeGroup> groups) {
            this.groups = groups;
            this.curr = new int[groups.size()];
            this.upperBound = new int[groups.size()];

            for (int i = 0; i < groups.size(); i++) {
                curr[i] = 0;
                upperBound[i] = groups.get(i).size();
            }
        }

        public BiGraphEdge[] next() {
            if (!hasNext())
                return null;

            // create returned solution
            BiGraphEdge[] solution = new BiGraphEdge[groups.size()];

            for (int i = 0; i < groups.size(); i++) {
                solution[i] = groups.get(i).get(curr[i]);
            }

            // next
            curr[groups.size() - 1]++;
            if (curr[groups.size() - 1] >= upperBound[groups.size() - 1]) {
                for (int i = groups.size() - 1; i >= 1; i--) {
                    if (curr[i] >= upperBound[i]) {
                        curr[i] = 0;
                        curr[i-1]++;
                    } else {
                        break;
                    }
                }
                if (curr[0] >= upperBound[0])
                    groups = null;
            }
            return solution;
        }

        public boolean hasNext() {
            return groups != null && groups.size() != 0;
        }
    }
}
